Creating new pipeline using seurat v4.0.2 available 2021.06.08
Load libraries required for Seuratv4
knitr::opts_knit$set(root.dir = "~/Desktop/10XGenomicsData/msAggr_scRNASeq/")
library(dplyr)
library(Seurat)
library(patchwork)
library(ggplot2)
# library(clustree)
store session info
# sink("CMP-v1.20210616.txt")
sessionInfo()
# sink()
Set global variables
projectName <- "CMP"
jackstraw.dim <- 40
source("msAggr_AnalysisCode/read_10XGenomics_data.R")
source("msAggr_AnalysisCode/PercentVariance.R")
source("msAggr_AnalysisCode/Mouse2Human_idconversion.R")
setwd("../cellRanger/") # temporarily changing wd only works if you run the entire chunk at once
data_file.list <- read_10XGenomics_data(sample.list = "CMPm2")
object.data <-Read10X(data_file.list)
cmp.object<- CreateSeuratObject(counts = object.data, min.cells = 3, min.genes = 200, project = projectName)
Clean up to free memory
remove(object.data)
Add mitochondrial metadata and plot some basic features
cmp.object[["percent.mt"]] <- PercentageFeatureSet(cmp.object, pattern = "^mt-")
VlnPlot(cmp.object, features = c("nFeature_RNA", "nCount_RNA", "percent.mt"), ncol = 3, pt.size = 0, fill.by = 'orig.ident', )
plot1 <- FeatureScatter(cmp.object, feature1 = "nCount_RNA", feature2 = "percent.mt", group.by = "orig.ident", pt.size = 0.01)
plot2 <- FeatureScatter(cmp.object, feature1 = "nCount_RNA", feature2 = "nFeature_RNA", group.by = "orig.ident", pt.size = 0.01)
plot1 + plot2
We don’t have to worry about comparing library depths, so we’ll just do normalization/Scale data
remove low quality cells require: nFeature_RNA between 200 and 4000 (inclusive) require: percent.mt <=5
print(paste("original object:", nrow(cmp.object@meta.data), "cells", sep = " "))
cmp.object <- subset(cmp.object,
subset = nFeature_RNA >=200 &
nFeature_RNA <= 4000 &
percent.mt <= 5
)
print(paste("new object:", nrow(cmp.object@meta.data), "cells", sep = " "))
cmp.object <- NormalizeData(cmp.object, normalization.method = "LogNormalize", scale.factor = 10000)
Find variable features
cmp.object <- FindVariableFeatures(cmp.object, selection.method = "vst", nfeatures = 2000)
top10 <- head(VariableFeatures(cmp.object), 10)
plot1 <- VariableFeaturePlot(cmp.object)
plot2 <- LabelPoints(plot = plot1, points = top10, repel = TRUE)
plot1 + plot2
Scale data (linear transformation)
Save raw object
saveRDS(cmp.object, file = paste0(projectName, "_raw.RDS"))
cmp.object <- RunPCA(cmp.object, features = VariableFeatures(cmp.object), ndims.print = 1:5, nfeatures.print = 5)
DimPlot(cmp.object, reduction = "pca", group.by = "orig.ident")
VizDimLoadings(cmp.object, dims = 1:6, nfeatures = 10, reduction = "pca", ncol = 3)
Calculate dimensionality
ElbowPlot(cmp.object, ndims = 50)
percent.variance(cmp.object@reductions$pca@stdev)
Number of PCs describing X% of variance
tot.var <- percent.variance(cmp.object@reductions$pca@stdev, plot.var = FALSE, return.val = TRUE)
paste0("Num pcs for 80% variance:", length(which(cumsum(tot.var) <= 80)))
paste0("Num pcs for 85% variance:", length(which(cumsum(tot.var) <= 85)))
paste0("Num pcs for 90% variance:", length(which(cumsum(tot.var) <= 90)))
paste0("Num pcs for 95% variance:", length(which(cumsum(tot.var) <= 95)))
Add cluster IDs from Seurat v1
Exported cell IDs for clusters 3, 17, 10, 11 from Seurat v1. Will add these IDs as a metadata column.
Create column “clust.ID” and populate with 0’s. Then import IDs for clusters
clust3.cells <- read.table(file = "Seuratv1_clusterCellIDs/cluster3cellIDs.txt", col.names = "clust03")
clust3.cells <- sapply(clust3.cells, function(x) paste0(gsub("CMP", "CMPm2", x), "-1"))
clust17.cells <- read.table(file = "Seuratv1_clusterCellIDs/cluster17cellIDs.txt", col.names = "clust17")
clust17.cells <- sapply(clust17.cells, function(x) paste0(gsub("CMP", "CMPm2", x), "-1"))
clust10.cells <- read.table(file = "Seuratv1_clusterCellIDs/cluster10cellIDs.txt", col.names = "clust10")
clust10.cells <- sapply(clust10.cells, function(x) paste0(gsub("CMP", "CMPm2", x), "-1"))
clust11.cells <- read.table(file = "Seuratv1_clusterCellIDs/cluster11cellIDs.txt", col.names = "clust11")
clust11.cells <- sapply(clust11.cells, function(x) paste0(gsub("CMP", "CMPm2", x), "-1"))
Add new metadata column
cmp.object@meta.data['clust.ID'] <- 0
head(cmp.object@meta.data)
now map new ids
cmp.object@meta.data$clust.ID[rownames(cmp.object@meta.data) %in% clust3.cells] <- 3
cmp.object@meta.data$clust.ID[rownames(cmp.object@meta.data) %in% clust17.cells] <- 17
cmp.object@meta.data$clust.ID[rownames(cmp.object@meta.data) %in% clust10.cells] <- 10
cmp.object@meta.data$clust.ID[rownames(cmp.object@meta.data) %in% clust11.cells] <- 11
do numbers make sense?
nrow(cmp.object@meta.data[cmp.object@meta.data$clust.ID == 10,])
nrow(cmp.object@meta.data[cmp.object@meta.data$clust.ID == 11,])
nrow(cmp.object@meta.data[cmp.object@meta.data$clust.ID == 17,])
nrow(cmp.object@meta.data[cmp.object@meta.data$clust.ID == 3,])
Color palette
color.palette <- c(
"coral",
"chartreuse4",
"goldenrod1",
"cadetblue1",
"burlywood",
"brown",
"brown1",
"blue",
"blue4",
"azure3",
"aquamarine",
"antiquewhite",
"cadetblue",
"gold3",
"black",
"darkgreen",
"deeppink",
"darkviolet",
"darkturquoise",
"darkslategray",
"darksalmon",
"darkorchid1",
"darkolivegreen2",
"forestgreen",
"dodgerblue",
"green",
"lightpink",
"lightcoral",
"khaki1",
"maroon",
"peru",
"lightseagreen",
"lightsalmon",
"plum",
"moccasin",
"tan",
"tan1",
"red",
"purple",
"khaki4",
"black",
"plum4"
)
Total var 90%
Neighborhood and umap
set total.var <- 90%
color.palette <- c(
"coral",
"chartreuse4",
"goldenrod1",
"cadetblue1",
"burlywood",
"brown",
"brown1",
"blue",
"blue4",
"azure3",
"aquamarine",
"antiquewhite",
"cadetblue",
"gold3",
"black",
"darkgreen",
"deeppink",
"darkviolet",
"darkturquoise",
"darkslategray",
"darksalmon",
"darkorchid1",
"darkolivegreen2",
"forestgreen",
"dodgerblue",
"green",
"lightpink",
"lightcoral",
"khaki1",
"maroon",
"peru",
"lightseagreen",
"lightsalmon",
"plum",
"moccasin",
"tan",
"tan1",
"red",
"purple",
"khaki4",
"black",
"plum4"
)
Plot UMAP
cmp.object <- FindNeighbors(cmp.object, dims = 1:ndims)
Computing nearest neighbor graph
Computing SNN
cmp.object <- FindNeighbors(cmp.object, dims = 1:ndims)
Computing nearest neighbor graph
Computing SNN
cmp.object <- FindClusters(cmp.object, resolution = 0.5)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
Number of nodes: 12059
Number of edges: 452999
Running Louvain algorithm...
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.8502
Number of communities: 9
Elapsed time: 1 seconds
cmp.object <- RunUMAP(cmp.object, dims = 1: ndims)
Warning: The default method for RunUMAP has changed from calling Python UMAP via reticulate to the R-native UWOT using the cosine metric
To use Python UMAP via reticulate, set umap.method to 'umap-learn' and metric to 'correlation'
This message will be shown once per session
15:09:54 UMAP embedding parameters a = 0.9922 b = 1.112
15:09:54 Read 12059 rows and found 33 numeric columns
15:09:54 Using Annoy for neighbor search, n_neighbors = 30
15:09:54 Building Annoy index with metric = cosine, n_trees = 50
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:09:55 Writing NN index file to temp file /var/folders/4f/fwrj6fnn1dn4g8wsf0zv563hjsvl24/T//RtmpYaUTrq/file158c04b44b74a
15:09:55 Searching Annoy index using 1 thread, search_k = 3000
15:09:58 Annoy recall = 100%
15:09:59 Commencing smooth kNN distance calibration using 1 thread
15:09:59 Initializing from normalized Laplacian + noise
15:10:00 Commencing optimization for 200 epochs, with 513294 positive edges
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:10:06 Optimization finished
for(x in c(0.5, 1, 1.5, 2, 2.5)){
cmp.object <- FindClusters(cmp.object, resolution = x)
}
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
Number of nodes: 12059
Number of edges: 452999
Running Louvain algorithm...
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.8502
Number of communities: 9
Elapsed time: 1 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
Number of nodes: 12059
Number of edges: 452999
Running Louvain algorithm...
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.7974
Number of communities: 17
Elapsed time: 1 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
Number of nodes: 12059
Number of edges: 452999
Running Louvain algorithm...
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.7652
Number of communities: 22
Elapsed time: 1 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
Number of nodes: 12059
Number of edges: 452999
Running Louvain algorithm...
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.7404
Number of communities: 27
Elapsed time: 1 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
Number of nodes: 12059
Number of edges: 452999
Running Louvain algorithm...
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.7189
Number of communities: 30
Elapsed time: 1 seconds
for each resolution, number/percentage of cells in each cluster?
for (meta.col in colnames(cmp.object@meta.data)){
if(grepl(pattern = ("RNA_snn_res"), x = meta.col)==TRUE){
myplot <- DimPlot(cmp.object,
group.by = meta.col,
reduction = "umap",
cols = color.palette
) +
ggtitle(paste0(projectName, " dim", ndims, "res", gsub("RNA_snn_res", "", meta.col) ))
plot(myplot)
}
}





For each resolution, what percentage of cells in each cluster are enriched for one of our clust.IDs?
Test: what percentage of each new clusterID matches one of the older clusters?
tot.cells <- nrow(cmp.object@meta.data)
for (meta.col in colnames(cmp.object@meta.data)){
if(grepl(pattern = ("RNA_snn_res"), x = meta.col)==TRUE){
new.clusters <- sort(as.numeric(levels(cmp.object@meta.data[[meta.col]])))
stats.df <- data.frame(matrix(ncol = 2, nrow = length(new.clusters)))
colnames(stats.df) <- c("num_cells", "pct_pop")
rownames(stats.df) <- new.clusters
meta.df <- cmp.object@meta.data
for(row.id in rownames(stats.df)){
num.x <- nrow(meta.df[meta.df[meta.col] == row.id,])
pct.x <- as.integer(num.x / tot.cells *100)
# print(pct.x)
stats.df[row.id, "num_cells"] <- num.x
stats.df[row.id, "pct_pop"] <- pct.x
}
print(stats.df)
}
}
Absolutely terrible overlap, no enrichment of any of these across the new clustering algorithm. Maybe should try 95% variation covered
Find old cells on UMAP
time for the super scarey moment to see if the cells from seuratv1 still cluster together on in seurat v4
DimPlot(cmp.object,
reduction = "umap",
group.by = "clust.ID",
# split.by = "orig.ident",
cols = c("gray", "orange", "blue", "red", "green"),)
DimPlot(cmp.object,
reduction = "umap",
group.by = "orig.ident",
split.by = "clust.ID",
cols = c("gray", "orange", "blue", "red", "green"),)
Total var 95%
Neighborhood and umap
set total.var <- 95%
tot.var <- percent.variance(cmp.object@reductions$pca@stdev, plot.var = FALSE, return.val = TRUE)
ndims <- length(which(cumsum(tot.var) <= 95))
cmp.object <- FindNeighbors(cmp.object, dims = 1:ndims)
cmp.object <- FindClusters(cmp.object, resolution = 0.5)
cmp.object <- RunUMAP(cmp.object, dims = 1: ndims)
saveRDS(cmp.object, file = paste0(projectName, "_dim", ndims, ".RDS"))
Plot UMAP
for(x in c(0.5, 1, 1.5, 2, 2.5)){
cmp.object <- FindClusters(cmp.object, resolution = x)
}
For each resolution, what percentage of cells in each cluster are enriched for one of our clust.IDs?
Test: what percentage of each new clusterID matches one of the older clusters?
for (meta.col in colnames(cmp.object@meta.data)){
if(grepl(pattern = ("RNA_snn_res"), x = meta.col)==TRUE){
new.clusters <- sort(as.numeric(levels(cmp.object@meta.data[[meta.col]])))
enrich.df <- data.frame(matrix(ncol = 4, nrow = length(new.clusters)))
colnames(enrich.df) <- c(3, 17, 10, 11)
rownames(enrich.df) <- new.clusters
meta.df <- cmp.object@meta.data
for(row.id in rownames(enrich.df)){
tot.clus <- nrow(meta.df[meta.df[[meta.col]] == row.id,])
for(col.id in colnames(enrich.df)){
num.x <- nrow(meta.df[(meta.df[[meta.col]] == row.id) & (meta.df$clust.ID == col.id),])
pct.x <- as.integer(num.x / tot.clus *100)
# print(pct.x)
enrich.df[row.id, col.id] <- pct.x
}
}
colnames(enrich.df) <- sapply(colnames(enrich.df), function(x) paste0("oldcluster", x))
rownames(enrich.df) <- sapply(rownames(enrich.df), function(x) paste0("newcluster", x))
xlsx::write.xlsx(enrich.df, file = paste0("PctOfNewClustersOverlappingOldClusters_", projectName, "_dim", ndims, ".xlsx"), sheetName = paste0(gsub("RNA_snn_", "", meta.col)), append = TRUE)
print(enrich.df)
}
}
Absolutely terrible overlap, no enrichment of any of these across the new clustering algorithm. Maybe should try 95% variation covered
Find old cells on UMAP
time for the super scarey moment to see if the cells from seuratv1 still cluster together on in seurat v4
DimPlot(cmp.object,
reduction = "umap",
group.by = "clust.ID",
pt.size = .1,
# split.by = "orig.ident",
cols = c("gray", "orange", "blue", "red", "green"),)
DimPlot(cmp.object,
reduction = "umap",
group.by = "orig.ident",
split.by = "clust.ID",
cols = c("gray", "orange", "blue", "red", "green"),)
Gene expression of old clustrs on new map
Let’s see if we can get some gene expression profiles on these…
gene.list <- c("Gata1", "Gata2", "Pf4", "Dntt", "Mpo", "Meis1", "Irf8", "Elane", "Fli1", "Zfpm1")
VlnPlot(cmp.object, features = gene.list, group.by = "clust.ID", pt.size = 0.01, cols = c("gray", "orange", "blue", "red", "green"))
Used the exce doc to do some fancy conditional formatting. Old cluster 17 is pretty dispersed until you it resolution 2.5. Otherise, cells in old cluster 17 do not constitute more than 40% of any cells in the new clusters.
As far as I can see, the two approaches are to do DGEof new CMP w/ resolution = 2.5, AND/OR do DGe using older cluster IDs. Sure seems to make sense to do both…
DGE w/ resolution = 2.5
Strt with comparing all clusters against all other clusters Write out cluster info
calculate FindAllMarkers() for different idents and save to new file
ident.list <- c("RNA_snn_res.0.5", "RNA_snn_res.1", "RNA_snn_res.1.5", "RNA_snn_res.2", "RNA_snn_res.2.5", "clust.ID")
for(tested.ident in ident.list){
Idents(cmp.object) <- tested.ident
all.markers <- FindAllMarkers(cmp.object)
xlsx::write.xlsx(x = all.markers[,c("avg_log2FC", "p_val_adj", "cluster", "gene")],
file = paste0(projectName, "_FindALLMarkers_res2.5.xlsx"),
sheetName = tested.ident,
col.names = TRUE,
row.names = FALSE,
append = TRUE)
}
Create FindAllMarkers() lists for GSEA
Idents(cmp.object) <- "RNA_snn_res.2.5"
res.2.5.allmarkers <- FindAllMarkers(cmp.object)
Map HGNC symbols
Mouse2HumanTable <- Mouse2Human(res.2.5.allmarkers$gene)
HGNC <- with(Mouse2HumanTable, Mouse2HumanTable$HGNC[match(res.2.5.allmarkers$gene, Mouse2HumanTable$MGI)])
head(res.2.5.allmarkers)
res.2.5.allmarkers$HGNC <- HGNC
tail(res.2.5.allmarkers)
sig.res.2.5 <- res.2.5.allmarkers[res.2.5.allmarkers$p_val_adj <= 0.05, ]
sig.res.2.5 <- sig.res.2.5[c("avg_log2FC", "HGNC", "cluster")]
sig.res.2.5 <- sig.res.2.5[!(sig.res.2.5$HGNC == "" | is.na(sig.res.2.5$HGNC)),] # GSEA will fail if there are any blanks or NAs in the table
sig.res.2.5 <- sig.res.2.5[]
for(cluster in unique(sig.res.2.5$cluster)){
print(paste("writing cluster", cluster))
new.table <- sig.res.2.5[sig.res.2.5$cluster == cluster, c("HGNC", "avg_log2FC")]
new.table <- new.table[order(-new.table$avg_log2FC), ]
write.table(new.table, file = paste0("RankList_res2.5_findAll_hgnc/res.2.5cluster", cluster, ".rnk"), quote = FALSE, row.names = FALSE, col.names = TRUE, sep = "\t", )
}
calculate FindMarkers() that distinguish each cluster (might overlab between clusters)
ident.list <- c("RNA_snn_res.0.5", "RNA_snn_res.1", "RNA_snn_res.1.5", "RNA_snn_res.2", "RNA_snn_res.2.5", "clust.ID")
for(tested.ident in ident.list){
for (cluster in sort(as.numeric(levels(cmp.object@meta.data[[tested.ident]])))){
cluster.markers <- FindMarkers(cmp.object, ident.1 = cluster)
xlsx::write.xlsx(x = cluster.markers[,c("avg_log2FC", "p_val_adj")],
file = paste0(projectName, "_FindMarkers_", gsub("RNA_snn_", "", tested.ident), ".xlsx"),
sheetName = paste0("clst", cluster),
col.names = TRUE,
row.names = TRUE,
append = TRUE)
}
}
for (cluster in sort(as.numeric(levels(cmp.object@meta.data$RNA_snn_res.2.5)))){
cluster.markers <- FindMarkers(cmp.object, ident.1 = cluster)
xlsx::write.xlsx(x = cluster.markers[,c("avg_log2FC", "p_val_adj")],
file = paste0(projectName, "_FindMarkers_res2.5.xlsx"),
sheetName = paste0("clst", cluster),
col.names = TRUE,
row.names = TRUE,
append = TRUE)
}
Combine clusters that might represent old cluster ids
Distinguishing features of clusters
Previously defined biomark genes based on PC contributions. Original list was based on all msAggr, but let’s see how CMP subset does?
VizDimLoadings(cmp.object, dims = 1:10, nfeatures = 30, reduction = "pca", ncol = 2)
pca.df <- cmp.object[["pca"]]
pca.df <- as.data.frame(as.matrix(slot(object = pca.df, name = "feature.loadings")))
print(cmp.object[["pca"]], dims = 2, nfeatures = 5)
rownames(pca.df[pca.df$PC_2 %in% sort(pca.df$PC_2, decreasing = TRUE)[1:5], ])
rownames(pca.df[pca.df$PC_2 %in% sort(pca.df$PC_2)[1:5], ])
now we can get a list of principal components!
first pull the list of oldAnalysis CMP top PC genes
cmp.biomark <- read.table(file = "/Users/heustonef/Desktop/CMPSubpops/BioMark/ProbePanels/CMP_PCTopGenes.txt", sep = "\t", header = TRUE)
biomark.cmptargets <- c()
for(df.col in 1:ncol(cmp.biomark)){
biomark.cmptargets <- c(biomark.cmptargets, biomark[,df.col])
}
print(colnames(biomark))
print(paste("total gene count:", length(biomark.cmptargets)))
Now get the list of current pc gene trgets (oldAnalysis used ndim = 1:6, so we’ll start with that range)
pc.list <- c("PC_1", "PC_2", "PC_3", "PC_4", "PC_5", "PC_6")
pc.genes <- lapply(pc.list, function(x) rownames(pca.df[pca.df[[x]] %in% sort(pca.df[[x]], decreasing = TRUE)[1:30],])) #targeting roughly 180 genes like in biomark.cmptargets
pc.genes <- unique(unlist(pc.genes))
print(paste("total gene count:", length(pc.genes)))
Now compare the lists, I guess:
# setdiff(x,y) gives you things in x not in y. setdiff(y,x) gives you things in y not in x
setdiff(biomark.cmptargets, pc.genes)
# print(paste("\n length:", length(setdiff(biomark.cmptargets, pc.genes))))
writeLines(c("", "length:", length(setdiff(biomark.cmptargets, pc.genes))))
Umm, yeah that went kinda how I expected. Let’s do this again, but for the actual biomark gene lists.
biomark <- read.table(file = "/Users/heustonef/Desktop/CMPSubpops/BioMark/ProbePanels/BiomarkProbeList.txt", sep = "\t")
biomark <- biomark[,1]
setdiff(biomark, pc.genes)
writeLines(c("", "length:", length(setdiff(biomark, pc.genes))))
What if we increase the number of pcs but decrease the depth of each? This might cover more of biomark, which was originally developed using msAggr instead of only the CMP subset
pc.list <- c("PC_1", "PC_2", "PC_3", "PC_4", "PC_5", "PC_6", "PC_7", "PC_8", "PC_9", "PC_10")
pc.genes <- lapply(pc.list, function(x) rownames(pca.df[pca.df[[x]] %in% sort(pca.df[[x]], decreasing = TRUE)[1:20],]))
pc.genes <- unique(unlist(pc.genes))
print(paste("total gene count:", length(pc.genes)))
setdiff(biomark, pc.genes)
writeLines(c("", "length:", length(setdiff(biomark, pc.genes))))
For comparison, let’s just see how many of biomark.cmptargets were actually included in biomark
setdiff(biomark.cmptargets, biomark)
writeLines(c("", "length:", length(setdiff(biomark.cmptargets, pc.genes))))
length(biomark) - length(setdiff(biomark, biomark.cmptargets))
length(biomark) - length(setdiff(biomark, pc.genes))
So when you look at it like that, it’s not actually that far off.
What are the similarities?:
setdiff(setdiff(biomark, biomark.cmptargets), setdiff(biomark, pc.genes))
These are genes from the 97probes not in the old CMP set that are also not in the new CMP set. Other than Itga2b (which is a failed probe anyway), nothing screams. Also we’d have thrown Flt3 and Cd34 for in anyway because they’re requisite cell surface markers (also Flt3 surface marker is expensive but otherwise not noteworthy and not used in the current sorting strategy)
What about cell surface marker expression? * Cd34 * Cd16/32 * Cd9 * Cd41 * Cd48 * Sca1 (just throw that in for sh*&s and giggles)
surface.markers <- c("Cd34", "Fcgr3", "Fcgr2b", "Cd9", "Itga2b", "Cd48", "Ly6a")
FeaturePlot(cmp.object, features = surface.markers, pt.size = 1, split.by = "clust.ID", ncol = 1)
Save as png
png(filename = "FeaturePlot_CMP_surfaceMarkers_clustIDfacet.png", height = 1600, width = 1600)
FeaturePlot(cmp.object, features = surface.markers, pt.size = 1, split.by = "clust.ID", ncol = 1)
dev.off()
Cell cycle analysis
Just so we know what we’re working with
s.genes <- cc.genes.updated.2019$s.genes
g2m.genes <- cc.genes.updated.2019$g2m.genes
Compare @ hierarchcial clusteirng
Do clustering using biomark RNAs as input
# Read in BiomarkRNAs
biomark.rnas <- read.table('/Users/heustonef/Desktop/10XGenomicsData/BiomarkRNAs.txt')
biomark.rnas <- biomark.rnas$V1
use biomark RNAs to define dimensional reduction
cmp.object <- readRDS("CMP_raw.RDS")
cmp.object <- RunPCA(cmp.object, features = biomark.rnas, ndims.print = 1:5, , nfeatures.print = 5)
ElbowPlot(cmp.object, ndims = 50)
Now run the clustering
tot.var <- percent.variance(cmp.object@reductions$pca@stdev, plot.var = FALSE, return.val = TRUE)
ndims <- length(which(cumsum(tot.var) <= 90))
cmp.object <- FindNeighbors(cmp.object, dims = 1:ndims)
cmp.object <- FindClusters(cmp.object, resolution = 0.5)
cmp.object <- RunUMAP(cmp.object, dims = 1: ndims)
find the clusters
for(x in c(0.5, 1, 1.5, 2, 2.5)){
cmp.object <- FindClusters(cmp.object, resolution = x)
}
Plot the umaps and cell cluster ids
for (meta.col in colnames(cmp.object@meta.data)){
if(grepl(pattern = ("RNA_snn_res"), x = meta.col)==TRUE){
myplot <- DimPlot(cmp.object,
group.by = meta.col,
reduction = "umap",
cols = color.palette
) +
ggtitle(paste0(projectName, " dim", ndims, "res", gsub("RNA_snn_res", "", meta.col) ))
plot(myplot)
}
}
Calculate anticipated number of cells you’ll find in each biomark cluster
Get # cells in each cluster
tot.cellcount <- nrow(cmp.object@meta.data)
res05.list <- sort(unique(cmp.object@meta.data$RNA_snn_res.0.5), decreasing = FALSE)
sapply(res05.list,
function(x){
print(
paste(
"cluster", x, "=",
nrow(cmp.object@meta.data[cmp.object@meta.data$RNA_snn_res.0.5 == x,]),
"cells or",
round(nrow(cmp.object@meta.data[cmp.object@meta.data$RNA_snn_res.0.5 == x,])/tot.cellcount*100, digits = 2),
"% of total"
)
)
}
)
So we did the dimensional reduction based on the biomark RNAs, then did our UMAP nearest neighbor clustering.
In the biomark hierarchcial clustering analysis I assayed 167 cells. The smallest cluster I detected had 3 cells, or 1.8% of total, and this is an uncomfortably small number of cells. Based on the UMAP calculations I would therefore expect to find 11 or 12 of the predicted 15 clusters. I found 12, and I don’t really like that last one, so 11 or 12. Since I did the hierarchcial clustering yesterday and did this math today, we can say it was independent of these results and therefore totally legit. Yay!!
LS0tCnRpdGxlOiAiQ01QU3Vic2V0IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpDcmVhdGluZyBuZXcgcGlwZWxpbmUgdXNpbmcgc2V1cmF0IHY0LjAuMiBhdmFpbGFibGUgMjAyMS4wNi4wOAoKTG9hZCBsaWJyYXJpZXMgcmVxdWlyZWQgZm9yIFNldXJhdHY0CgpgYGB7ciBzZXR1cH0Ka25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSAifi9EZXNrdG9wLzEwWEdlbm9taWNzRGF0YS9tc0FnZ3Jfc2NSTkFTZXEvIikKbGlicmFyeShkcGx5cikKbGlicmFyeShTZXVyYXQpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KGdncGxvdDIpCiMgbGlicmFyeShjbHVzdHJlZSkKYGBgCnN0b3JlIHNlc3Npb24gaW5mbwpgYGB7ciB9CiMgc2luaygiQ01QLXYxLjIwMjEwNjE2LnR4dCIpCnNlc3Npb25JbmZvKCkKIyBzaW5rKCkKYGBgCgojIFNldCBnbG9iYWwgdmFyaWFibGVzCmBgYHtyfQpwcm9qZWN0TmFtZSA8LSAiQ01QIgpqYWNrc3RyYXcuZGltIDwtIDQwCmBgYAoKCgpgYGB7cn0Kc291cmNlKCJtc0FnZ3JfQW5hbHlzaXNDb2RlL3JlYWRfMTBYR2Vub21pY3NfZGF0YS5SIikKc291cmNlKCJtc0FnZ3JfQW5hbHlzaXNDb2RlL1BlcmNlbnRWYXJpYW5jZS5SIikKc291cmNlKCJtc0FnZ3JfQW5hbHlzaXNDb2RlL01vdXNlMkh1bWFuX2lkY29udmVyc2lvbi5SIikKYGBgCgoKYGBge3J9CnNldHdkKCIuLi9jZWxsUmFuZ2VyLyIpICMgdGVtcG9yYXJpbHkgY2hhbmdpbmcgd2Qgb25seSB3b3JrcyBpZiB5b3UgcnVuIHRoZSBlbnRpcmUgY2h1bmsgYXQgb25jZQpkYXRhX2ZpbGUubGlzdCA8LSByZWFkXzEwWEdlbm9taWNzX2RhdGEoc2FtcGxlLmxpc3QgPSAiQ01QbTIiKQpvYmplY3QuZGF0YSA8LVJlYWQxMFgoZGF0YV9maWxlLmxpc3QpCmBgYAoKCgpgYGB7cn0KY21wLm9iamVjdDwtIENyZWF0ZVNldXJhdE9iamVjdChjb3VudHMgPSBvYmplY3QuZGF0YSwgbWluLmNlbGxzID0gMywgbWluLmdlbmVzID0gMjAwLCBwcm9qZWN0ID0gcHJvamVjdE5hbWUpCmBgYAoKQ2xlYW4gdXAgdG8gZnJlZSBtZW1vcnkKCmBgYHtyfQpyZW1vdmUob2JqZWN0LmRhdGEpCmBgYAoKCkFkZCBtaXRvY2hvbmRyaWFsIG1ldGFkYXRhIGFuZCBwbG90IHNvbWUgYmFzaWMgZmVhdHVyZXMKYGBge3J9CmNtcC5vYmplY3RbWyJwZXJjZW50Lm10Il1dIDwtIFBlcmNlbnRhZ2VGZWF0dXJlU2V0KGNtcC5vYmplY3QsIHBhdHRlcm4gPSAiXm10LSIpClZsblBsb3QoY21wLm9iamVjdCwgZmVhdHVyZXMgPSBjKCJuRmVhdHVyZV9STkEiLCAibkNvdW50X1JOQSIsICJwZXJjZW50Lm10IiksIG5jb2wgPSAzLCBwdC5zaXplID0gMCwgZmlsbC5ieSA9ICdvcmlnLmlkZW50JywgKQpgYGAKCgpgYGB7cn0KcGxvdDEgPC0gRmVhdHVyZVNjYXR0ZXIoY21wLm9iamVjdCwgZmVhdHVyZTEgPSAibkNvdW50X1JOQSIsIGZlYXR1cmUyID0gInBlcmNlbnQubXQiLCBncm91cC5ieSA9ICJvcmlnLmlkZW50IiwgcHQuc2l6ZSA9IDAuMDEpCnBsb3QyIDwtIEZlYXR1cmVTY2F0dGVyKGNtcC5vYmplY3QsIGZlYXR1cmUxID0gIm5Db3VudF9STkEiLCBmZWF0dXJlMiA9ICJuRmVhdHVyZV9STkEiLCBncm91cC5ieSA9ICJvcmlnLmlkZW50IiwgcHQuc2l6ZSA9IDAuMDEpCnBsb3QxICsgcGxvdDIKYGBgCgpXZSBkb24ndCBoYXZlIHRvIHdvcnJ5IGFib3V0IGNvbXBhcmluZyBsaWJyYXJ5IGRlcHRocywgc28gd2UnbGwganVzdCBkbyBub3JtYWxpemF0aW9uL1NjYWxlIGRhdGEKCgoKcmVtb3ZlIGxvdyBxdWFsaXR5IGNlbGxzCnJlcXVpcmU6IG5GZWF0dXJlX1JOQSBiZXR3ZWVuIDIwMCBhbmQgNDAwMCAoaW5jbHVzaXZlKQpyZXF1aXJlOiBwZXJjZW50Lm10IDw9NQoKYGBge3J9CnByaW50KHBhc3RlKCJvcmlnaW5hbCBvYmplY3Q6IiwgbnJvdyhjbXAub2JqZWN0QG1ldGEuZGF0YSksICJjZWxscyIsIHNlcCA9ICIgIikpCmNtcC5vYmplY3QgPC0gc3Vic2V0KGNtcC5vYmplY3QsIAoJCQkJCQkJCQkJCQlzdWJzZXQgPSBuRmVhdHVyZV9STkEgPj0yMDAgJiAKCQkJCQkJCQkJCQkJCW5GZWF0dXJlX1JOQSA8PSA0MDAwICYgCgkJCQkJCQkJCQkJCQlwZXJjZW50Lm10IDw9IDUKCQkJCQkJCQkJCQkJKQpwcmludChwYXN0ZSgibmV3IG9iamVjdDoiLCBucm93KGNtcC5vYmplY3RAbWV0YS5kYXRhKSwgImNlbGxzIiwgc2VwID0gIiAiKSkKYGBgCgoKCmBgYHtyfQpjbXAub2JqZWN0IDwtIE5vcm1hbGl6ZURhdGEoY21wLm9iamVjdCwgbm9ybWFsaXphdGlvbi5tZXRob2QgPSAiTG9nTm9ybWFsaXplIiwgc2NhbGUuZmFjdG9yID0gMTAwMDApCmBgYAoKCkZpbmQgdmFyaWFibGUgZmVhdHVyZXMKYGBge3IgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDJ9CmNtcC5vYmplY3QgPC0gRmluZFZhcmlhYmxlRmVhdHVyZXMoY21wLm9iamVjdCwgc2VsZWN0aW9uLm1ldGhvZCA9ICJ2c3QiLCBuZmVhdHVyZXMgPSAyMDAwKQp0b3AxMCA8LSBoZWFkKFZhcmlhYmxlRmVhdHVyZXMoY21wLm9iamVjdCksIDEwKQpwbG90MSA8LSBWYXJpYWJsZUZlYXR1cmVQbG90KGNtcC5vYmplY3QpCnBsb3QyIDwtIExhYmVsUG9pbnRzKHBsb3QgPSBwbG90MSwgcG9pbnRzID0gdG9wMTAsIHJlcGVsID0gVFJVRSkKcGxvdDEgKyBwbG90MgoKYGBgCgpTY2FsZSBkYXRhIChsaW5lYXIgdHJhbnNmb3JtYXRpb24pCgpgYGB7ciBlY2hvID0gRkFMU0V9CmFsbC5nZW5lcyA8LSByb3duYW1lcyhjbXAub2JqZWN0KQpjbXAub2JqZWN0IDwtIFNjYWxlRGF0YShjbXAub2JqZWN0LCBmZWF0dXJlcyA9IGFsbC5nZW5lcywgdmFycy50by5yZWdyZXNzID0gYygibkZlYXR1cmVfUk5BIiwgIm5Db3VudF9STkEiKSkKYGBgCgojIyBTYXZlIHJhdyBvYmplY3QKYGBge3J9CnNhdmVSRFMoY21wLm9iamVjdCwgZmlsZSA9IHBhc3RlMChwcm9qZWN0TmFtZSwgIl9yYXcuUkRTIikpCmBgYAoKCgpgYGB7cn0KY21wLm9iamVjdCA8LSBSdW5QQ0EoY21wLm9iamVjdCwgZmVhdHVyZXMgPSBWYXJpYWJsZUZlYXR1cmVzKGNtcC5vYmplY3QpLCBuZGltcy5wcmludCA9IDE6NSwgbmZlYXR1cmVzLnByaW50ID0gNSkKYGBgCgpgYGB7cn0KRGltUGxvdChjbXAub2JqZWN0LCByZWR1Y3Rpb24gPSAicGNhIiwgZ3JvdXAuYnkgPSAib3JpZy5pZGVudCIpClZpekRpbUxvYWRpbmdzKGNtcC5vYmplY3QsIGRpbXMgPSAxOjYsIG5mZWF0dXJlcyA9IDEwLCByZWR1Y3Rpb24gPSAicGNhIiwgbmNvbCA9IDMpCgpgYGAKCkNhbGN1bGF0ZSBkaW1lbnNpb25hbGl0eQpgYGB7ciwgZmlndXJlcy1zaWRlLCBmaWcuc2hvdz0naG9sZCcsIG91dC53aWR0aD0iNTAlIn0KRWxib3dQbG90KGNtcC5vYmplY3QsIG5kaW1zID0gNTApCnBlcmNlbnQudmFyaWFuY2UoY21wLm9iamVjdEByZWR1Y3Rpb25zJHBjYUBzdGRldikKYGBgCk51bWJlciBvZiBQQ3MgZGVzY3JpYmluZyBYJSBvZiB2YXJpYW5jZQoKYGBge3J9CnRvdC52YXIgPC0gcGVyY2VudC52YXJpYW5jZShjbXAub2JqZWN0QHJlZHVjdGlvbnMkcGNhQHN0ZGV2LCBwbG90LnZhciA9IEZBTFNFLCByZXR1cm4udmFsID0gVFJVRSkKcGFzdGUwKCJOdW0gcGNzIGZvciA4MCUgdmFyaWFuY2U6IiwgbGVuZ3RoKHdoaWNoKGN1bXN1bSh0b3QudmFyKSA8PSA4MCkpKQpwYXN0ZTAoIk51bSBwY3MgZm9yIDg1JSB2YXJpYW5jZToiLCBsZW5ndGgod2hpY2goY3Vtc3VtKHRvdC52YXIpIDw9IDg1KSkpCnBhc3RlMCgiTnVtIHBjcyBmb3IgOTAlIHZhcmlhbmNlOiIsIGxlbmd0aCh3aGljaChjdW1zdW0odG90LnZhcikgPD0gOTApKSkKcGFzdGUwKCJOdW0gcGNzIGZvciA5NSUgdmFyaWFuY2U6IiwgbGVuZ3RoKHdoaWNoKGN1bXN1bSh0b3QudmFyKSA8PSA5NSkpKQoKYGBgCgojIEFkZCBjbHVzdGVyIElEcyBmcm9tIFNldXJhdCB2MQoKRXhwb3J0ZWQgY2VsbCBJRHMgZm9yIGNsdXN0ZXJzIDMsIDE3LCAxMCwgMTEgZnJvbSBTZXVyYXQgdjEuIFdpbGwgYWRkIHRoZXNlIElEcyBhcyBhIG1ldGFkYXRhIGNvbHVtbi4gIApDcmVhdGUgY29sdW1uICJjbHVzdC5JRCIgYW5kIHBvcHVsYXRlIHdpdGggMCdzLiBUaGVuIGltcG9ydCBJRHMgZm9yIGNsdXN0ZXJzCgoKCmBgYHtyfQpjbHVzdDMuY2VsbHMgPC0gcmVhZC50YWJsZShmaWxlID0gIlNldXJhdHYxX2NsdXN0ZXJDZWxsSURzL2NsdXN0ZXIzY2VsbElEcy50eHQiLCBjb2wubmFtZXMgPSAiY2x1c3QwMyIpCmNsdXN0My5jZWxscyA8LSBzYXBwbHkoY2x1c3QzLmNlbGxzLCBmdW5jdGlvbih4KSBwYXN0ZTAoZ3N1YigiQ01QIiwgIkNNUG0yIiwgeCksICItMSIpKQpjbHVzdDE3LmNlbGxzIDwtIHJlYWQudGFibGUoZmlsZSA9ICJTZXVyYXR2MV9jbHVzdGVyQ2VsbElEcy9jbHVzdGVyMTdjZWxsSURzLnR4dCIsIGNvbC5uYW1lcyA9ICJjbHVzdDE3IikKY2x1c3QxNy5jZWxscyA8LSBzYXBwbHkoY2x1c3QxNy5jZWxscywgZnVuY3Rpb24oeCkgcGFzdGUwKGdzdWIoIkNNUCIsICJDTVBtMiIsIHgpLCAiLTEiKSkKY2x1c3QxMC5jZWxscyA8LSByZWFkLnRhYmxlKGZpbGUgPSAiU2V1cmF0djFfY2x1c3RlckNlbGxJRHMvY2x1c3RlcjEwY2VsbElEcy50eHQiLCBjb2wubmFtZXMgPSAiY2x1c3QxMCIpCmNsdXN0MTAuY2VsbHMgPC0gc2FwcGx5KGNsdXN0MTAuY2VsbHMsIGZ1bmN0aW9uKHgpIHBhc3RlMChnc3ViKCJDTVAiLCAiQ01QbTIiLCB4KSwgIi0xIikpCmNsdXN0MTEuY2VsbHMgPC0gcmVhZC50YWJsZShmaWxlID0gIlNldXJhdHYxX2NsdXN0ZXJDZWxsSURzL2NsdXN0ZXIxMWNlbGxJRHMudHh0IiwgY29sLm5hbWVzID0gImNsdXN0MTEiKQpjbHVzdDExLmNlbGxzIDwtIHNhcHBseShjbHVzdDExLmNlbGxzLCBmdW5jdGlvbih4KSBwYXN0ZTAoZ3N1YigiQ01QIiwgIkNNUG0yIiwgeCksICItMSIpKQpgYGAKCkFkZCBuZXcgbWV0YWRhdGEgY29sdW1uCmBgYHtyfQpjbXAub2JqZWN0QG1ldGEuZGF0YVsnY2x1c3QuSUQnXSA8LSAwCmhlYWQoY21wLm9iamVjdEBtZXRhLmRhdGEpCmBgYAoKbm93IG1hcCBuZXcgaWRzCmBgYHtyfQpjbXAub2JqZWN0QG1ldGEuZGF0YSRjbHVzdC5JRFtyb3duYW1lcyhjbXAub2JqZWN0QG1ldGEuZGF0YSkgJWluJSBjbHVzdDMuY2VsbHNdIDwtIDMKY21wLm9iamVjdEBtZXRhLmRhdGEkY2x1c3QuSURbcm93bmFtZXMoY21wLm9iamVjdEBtZXRhLmRhdGEpICVpbiUgY2x1c3QxNy5jZWxsc10gPC0gMTcKY21wLm9iamVjdEBtZXRhLmRhdGEkY2x1c3QuSURbcm93bmFtZXMoY21wLm9iamVjdEBtZXRhLmRhdGEpICVpbiUgY2x1c3QxMC5jZWxsc10gPC0gMTAKY21wLm9iamVjdEBtZXRhLmRhdGEkY2x1c3QuSURbcm93bmFtZXMoY21wLm9iamVjdEBtZXRhLmRhdGEpICVpbiUgY2x1c3QxMS5jZWxsc10gPC0gMTEKYGBgCgpkbyBudW1iZXJzIG1ha2Ugc2Vuc2U/CmBgYHtyfQpucm93KGNtcC5vYmplY3RAbWV0YS5kYXRhW2NtcC5vYmplY3RAbWV0YS5kYXRhJGNsdXN0LklEID09IDEwLF0pCm5yb3coY21wLm9iamVjdEBtZXRhLmRhdGFbY21wLm9iamVjdEBtZXRhLmRhdGEkY2x1c3QuSUQgPT0gMTEsXSkKbnJvdyhjbXAub2JqZWN0QG1ldGEuZGF0YVtjbXAub2JqZWN0QG1ldGEuZGF0YSRjbHVzdC5JRCA9PSAxNyxdKQpucm93KGNtcC5vYmplY3RAbWV0YS5kYXRhW2NtcC5vYmplY3RAbWV0YS5kYXRhJGNsdXN0LklEID09IDMsXSkKYGBgCgojIyMgQ29sb3IgcGFsZXR0ZQpgYGB7cn0KY29sb3IucGFsZXR0ZSA8LSBjKAoJImNvcmFsIiwKCSJjaGFydHJldXNlNCIsCgkiZ29sZGVucm9kMSIsCgkiY2FkZXRibHVlMSIsCgkiYnVybHl3b29kIiwKCSJicm93biIsCgkiYnJvd24xIiwKCSJibHVlIiwKCSJibHVlNCIsCgkiYXp1cmUzIiwKCSJhcXVhbWFyaW5lIiwKCSJhbnRpcXVld2hpdGUiLAoJImNhZGV0Ymx1ZSIsCgkiZ29sZDMiLAoJImJsYWNrIiwKCSJkYXJrZ3JlZW4iLAoJImRlZXBwaW5rIiwKCSJkYXJrdmlvbGV0IiwKCSJkYXJrdHVycXVvaXNlIiwKCSJkYXJrc2xhdGVncmF5IiwKCSJkYXJrc2FsbW9uIiwKCSJkYXJrb3JjaGlkMSIsCgkiZGFya29saXZlZ3JlZW4yIiwKCSJmb3Jlc3RncmVlbiIsCgkiZG9kZ2VyYmx1ZSIsCgkiZ3JlZW4iLAoJImxpZ2h0cGluayIsCgkibGlnaHRjb3JhbCIsCgkia2hha2kxIiwKCSJtYXJvb24iLAoJInBlcnUiLAoJImxpZ2h0c2VhZ3JlZW4iLAoJImxpZ2h0c2FsbW9uIiwKCSJwbHVtIiwKCSJtb2NjYXNpbiIsCgkidGFuIiwKCSJ0YW4xIiwgCgkicmVkIiwgCgkicHVycGxlIiwKCSJraGFraTQiLAoJImJsYWNrIiwgCgkicGx1bTQiCikKYGBgCgojIFRvdGFsIHZhciA5MCUKIyMgTmVpZ2hib3Job29kIGFuZCB1bWFwCnNldCB0b3RhbC52YXIgPC0gOTAlCmBgYHtyfQp0b3QudmFyIDwtIHBlcmNlbnQudmFyaWFuY2UoY21wLm9iamVjdEByZWR1Y3Rpb25zJHBjYUBzdGRldiwgcGxvdC52YXIgPSBGQUxTRSwgcmV0dXJuLnZhbCA9IFRSVUUpCm5kaW1zIDwtIGxlbmd0aCh3aGljaChjdW1zdW0odG90LnZhcikgPD0gOTApKQoKY21wLm9iamVjdCA8LSBGaW5kTmVpZ2hib3JzKGNtcC5vYmplY3QsIGRpbXMgPSAxOm5kaW1zKQpjbXAub2JqZWN0IDwtIEZpbmRDbHVzdGVycyhjbXAub2JqZWN0LCByZXNvbHV0aW9uID0gMC41KQpjbXAub2JqZWN0IDwtIFJ1blVNQVAoY21wLm9iamVjdCwgZGltcyA9IDE6IG5kaW1zKQoKc2F2ZVJEUyhjbXAub2JqZWN0LCBmaWxlID0gcGFzdGUwKHByb2plY3ROYW1lLCAiX2RpbSIsIG5kaW1zLCAiLlJEUyIpKQpgYGAKUGxvdCBVTUFQCgpgYGB7cn0KZm9yKHggaW4gYygwLjUsIDEsIDEuNSwgMiwgMi41KSl7CgljbXAub2JqZWN0IDwtIEZpbmRDbHVzdGVycyhjbXAub2JqZWN0LCByZXNvbHV0aW9uID0geCkKfQpgYGAKCmBgYHtyfQpmb3IgKG1ldGEuY29sIGluIGNvbG5hbWVzKGNtcC5vYmplY3RAbWV0YS5kYXRhKSl7CglpZihncmVwbChwYXR0ZXJuID0gKCJSTkFfc25uX3JlcyIpLCB4ID0gbWV0YS5jb2wpPT1UUlVFKXsKCQlteXBsb3QgPC0gRGltUGxvdChjbXAub2JqZWN0LCAKCQkJCQkJCQkJCQlncm91cC5ieSA9IG1ldGEuY29sLAoJCQkJCQkJCQkJCXJlZHVjdGlvbiA9ICJ1bWFwIiwgCgkJCQkJCQkJCQkJY29scyA9IGNvbG9yLnBhbGV0dGUKCQkJCQkJCQkJCQkpICsgCgkJCWdndGl0bGUocGFzdGUwKHByb2plY3ROYW1lLCAiIGRpbSIsIG5kaW1zLCAicmVzIiwgZ3N1YigiUk5BX3Nubl9yZXMiLCAiIiwgbWV0YS5jb2wpICkpCgkJcGxvdChteXBsb3QpCgl9Cn0KYGBgCgoKZm9yIGVhY2ggcmVzb2x1dGlvbiwgbnVtYmVyL3BlcmNlbnRhZ2Ugb2YgY2VsbHMgaW4gZWFjaCBjbHVzdGVyPwoKYGBge3J9CnRvdC5jZWxscyA8LSBucm93KGNtcC5vYmplY3RAbWV0YS5kYXRhKQpmb3IgKG1ldGEuY29sIGluIGNvbG5hbWVzKGNtcC5vYmplY3RAbWV0YS5kYXRhKSl7CglpZihncmVwbChwYXR0ZXJuID0gKCJSTkFfc25uX3JlcyIpLCB4ID0gbWV0YS5jb2wpPT1UUlVFKXsKCQluZXcuY2x1c3RlcnMgPC0gc29ydChhcy5udW1lcmljKGxldmVscyhjbXAub2JqZWN0QG1ldGEuZGF0YVtbbWV0YS5jb2xdXSkpKQoJCXN0YXRzLmRmIDwtIGRhdGEuZnJhbWUobWF0cml4KG5jb2wgPSAyLCBucm93ID0gbGVuZ3RoKG5ldy5jbHVzdGVycykpKQoJCWNvbG5hbWVzKHN0YXRzLmRmKSA8LSBjKCJudW1fY2VsbHMiLCAicGN0X3BvcCIpCgkJcm93bmFtZXMoc3RhdHMuZGYpIDwtIG5ldy5jbHVzdGVycwoJCW1ldGEuZGYgPC0gY21wLm9iamVjdEBtZXRhLmRhdGEKCQlmb3Iocm93LmlkIGluIHJvd25hbWVzKHN0YXRzLmRmKSl7CgkJCQludW0ueCA8LSBucm93KG1ldGEuZGZbbWV0YS5kZlttZXRhLmNvbF0gPT0gcm93LmlkLF0pCgkJCQlwY3QueCA8LSBhcy5pbnRlZ2VyKG51bS54IC8gdG90LmNlbGxzICoxMDApCgkJCQkjIHByaW50KHBjdC54KQoJCQkJc3RhdHMuZGZbcm93LmlkLCAibnVtX2NlbGxzIl0gPC0gbnVtLngKCQkJCXN0YXRzLmRmW3Jvdy5pZCwgInBjdF9wb3AiXSA8LSBwY3QueAoJCX0KCQlwcmludChzdGF0cy5kZikKCX0KfQpgYGAKCgoKRm9yIGVhY2ggcmVzb2x1dGlvbiwgd2hhdCBwZXJjZW50YWdlIG9mIGNlbGxzIGluIGVhY2ggY2x1c3RlciBhcmUgZW5yaWNoZWQgZm9yIG9uZSBvZiBvdXIgY2x1c3QuSURzPwoKClRlc3Q6IHdoYXQgcGVyY2VudGFnZSBvZiBlYWNoIG5ldyBjbHVzdGVySUQgbWF0Y2hlcyBvbmUgb2YgdGhlIG9sZGVyIGNsdXN0ZXJzPwpgYGB7cn0KZm9yIChtZXRhLmNvbCBpbiBjb2xuYW1lcyhjbXAub2JqZWN0QG1ldGEuZGF0YSkpewoJaWYoZ3JlcGwocGF0dGVybiA9ICgiUk5BX3Nubl9yZXMiKSwgeCA9IG1ldGEuY29sKT09VFJVRSl7CgkJbmV3LmNsdXN0ZXJzIDwtIHNvcnQoYXMubnVtZXJpYyhsZXZlbHMoY21wLm9iamVjdEBtZXRhLmRhdGFbW21ldGEuY29sXV0pKSkKCQllbnJpY2guZGYgPC0gZGF0YS5mcmFtZShtYXRyaXgobmNvbCA9IDQsIG5yb3cgPSBsZW5ndGgobmV3LmNsdXN0ZXJzKSkpCgkJY29sbmFtZXMoZW5yaWNoLmRmKSA8LSBjKDMsIDE3LCAxMCwgMTEpCgkJcm93bmFtZXMoZW5yaWNoLmRmKSA8LSBuZXcuY2x1c3RlcnMKCQltZXRhLmRmIDwtIGNtcC5vYmplY3RAbWV0YS5kYXRhCgkJZm9yKHJvdy5pZCBpbiByb3duYW1lcyhlbnJpY2guZGYpKXsKCQkJdG90LmNsdXMgPC0gbnJvdyhtZXRhLmRmW21ldGEuZGZbW21ldGEuY29sXV0gPT0gcm93LmlkLF0pCgkJCWZvcihjb2wuaWQgaW4gY29sbmFtZXMoZW5yaWNoLmRmKSl7CgkJCQludW0ueCA8LSBucm93KG1ldGEuZGZbKG1ldGEuZGZbW21ldGEuY29sXV0gPT0gcm93LmlkKSAmIChtZXRhLmRmJGNsdXN0LklEID09IGNvbC5pZCksXSkKCQkJCXBjdC54IDwtIGFzLmludGVnZXIobnVtLnggLyB0b3QuY2x1cyAqMTAwKQoJCQkJIyBwcmludChwY3QueCkKCQkJCWVucmljaC5kZltyb3cuaWQsIGNvbC5pZF0gPC0gcGN0LngKCQkJfQoJCX0KCQljb2xuYW1lcyhlbnJpY2guZGYpIDwtIHNhcHBseShjb2xuYW1lcyhlbnJpY2guZGYpLCBmdW5jdGlvbih4KSBwYXN0ZTAoIm9sZGNsdXN0ZXIiLCB4KSkKCQlyb3duYW1lcyhlbnJpY2guZGYpIDwtIHNhcHBseShyb3duYW1lcyhlbnJpY2guZGYpLCBmdW5jdGlvbih4KSBwYXN0ZTAoIm5ld2NsdXN0ZXIiLCB4KSkKCQl4bHN4Ojp3cml0ZS54bHN4KGVucmljaC5kZiwgZmlsZSA9IHBhc3RlMCgiUGN0T2ZOZXdDbHVzdGVyc092ZXJsYXBwaW5nT2xkQ2x1c3RlcnNfIiwgcHJvamVjdE5hbWUsICJfZGltIiwgbmRpbXMsICIueGxzeCIpLCBzaGVldE5hbWUgPSBwYXN0ZTAoZ3N1YigiUk5BX3Nubl8iLCAiIiwgbWV0YS5jb2wpKSwgYXBwZW5kID0gVFJVRSkKCQlwcmludChlbnJpY2guZGYpCgl9Cn0KCmBgYApBYnNvbHV0ZWx5IHRlcnJpYmxlIG92ZXJsYXAsIG5vIGVucmljaG1lbnQgb2YgYW55IG9mIHRoZXNlIGFjcm9zcyB0aGUgbmV3IGNsdXN0ZXJpbmcgYWxnb3JpdGhtLiBNYXliZSBzaG91bGQgdHJ5IDk1JSB2YXJpYXRpb24gY292ZXJlZAoKIyMgRmluZCBvbGQgY2VsbHMgb24gVU1BUAoKdGltZSBmb3IgdGhlIHN1cGVyIHNjYXJleSBtb21lbnQgdG8gc2VlIGlmIHRoZSBjZWxscyBmcm9tIHNldXJhdHYxIHN0aWxsIGNsdXN0ZXIgdG9nZXRoZXIgb24gaW4gc2V1cmF0IHY0CgpgYGB7ciBmaWcud2lkdGggPSA0fQpEaW1QbG90KGNtcC5vYmplY3QsCgkJCQlyZWR1Y3Rpb24gPSAidW1hcCIsCgkJCQlncm91cC5ieSA9ICJjbHVzdC5JRCIsIAoJCQkJIyBzcGxpdC5ieSA9ICJvcmlnLmlkZW50IiwKCQkJCWNvbHMgPSBjKCJncmF5IiwgIm9yYW5nZSIsICJibHVlIiwgInJlZCIsICJncmVlbiIpLCkKYGBgCmBgYHtyIGZpZy53aWR0aCA9IDR9CkRpbVBsb3QoY21wLm9iamVjdCwKCQkJCXJlZHVjdGlvbiA9ICJ1bWFwIiwKCQkJCWdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiLCAKCQkJCXNwbGl0LmJ5ID0gImNsdXN0LklEIiwKCQkJCWNvbHMgPSBjKCJncmF5IiwgIm9yYW5nZSIsICJibHVlIiwgInJlZCIsICJncmVlbiIpLCkKYGBgCgoKIyBUb3RhbCB2YXIgOTUlCiMjIE5laWdoYm9yaG9vZCBhbmQgdW1hcApzZXQgdG90YWwudmFyIDwtIDk1JQpgYGB7cn0KdG90LnZhciA8LSBwZXJjZW50LnZhcmlhbmNlKGNtcC5vYmplY3RAcmVkdWN0aW9ucyRwY2FAc3RkZXYsIHBsb3QudmFyID0gRkFMU0UsIHJldHVybi52YWwgPSBUUlVFKQpuZGltcyA8LSBsZW5ndGgod2hpY2goY3Vtc3VtKHRvdC52YXIpIDw9IDk1KSkKCmNtcC5vYmplY3QgPC0gRmluZE5laWdoYm9ycyhjbXAub2JqZWN0LCBkaW1zID0gMTpuZGltcykKY21wLm9iamVjdCA8LSBGaW5kQ2x1c3RlcnMoY21wLm9iamVjdCwgcmVzb2x1dGlvbiA9IDAuNSkKY21wLm9iamVjdCA8LSBSdW5VTUFQKGNtcC5vYmplY3QsIGRpbXMgPSAxOiBuZGltcykKCnNhdmVSRFMoY21wLm9iamVjdCwgZmlsZSA9IHBhc3RlMChwcm9qZWN0TmFtZSwgIl9kaW0iLCBuZGltcywgIi5SRFMiKSkKYGBgClBsb3QgVU1BUAoKYGBge3J9CmZvcih4IGluIGMoMC41LCAxLCAxLjUsIDIsIDIuNSkpewoJY21wLm9iamVjdCA8LSBGaW5kQ2x1c3RlcnMoY21wLm9iamVjdCwgcmVzb2x1dGlvbiA9IHgpCn0KYGBgCgoKCkZvciBlYWNoIHJlc29sdXRpb24sIHdoYXQgcGVyY2VudGFnZSBvZiBjZWxscyBpbiBlYWNoIGNsdXN0ZXIgYXJlIGVucmljaGVkIGZvciBvbmUgb2Ygb3VyIGNsdXN0LklEcz8KCgpUZXN0OiB3aGF0IHBlcmNlbnRhZ2Ugb2YgZWFjaCBuZXcgY2x1c3RlcklEIG1hdGNoZXMgb25lIG9mIHRoZSBvbGRlciBjbHVzdGVycz8KYGBge3J9CmZvciAobWV0YS5jb2wgaW4gY29sbmFtZXMoY21wLm9iamVjdEBtZXRhLmRhdGEpKXsKCWlmKGdyZXBsKHBhdHRlcm4gPSAoIlJOQV9zbm5fcmVzIiksIHggPSBtZXRhLmNvbCk9PVRSVUUpewoJCW5ldy5jbHVzdGVycyA8LSBzb3J0KGFzLm51bWVyaWMobGV2ZWxzKGNtcC5vYmplY3RAbWV0YS5kYXRhW1ttZXRhLmNvbF1dKSkpCgkJZW5yaWNoLmRmIDwtIGRhdGEuZnJhbWUobWF0cml4KG5jb2wgPSA0LCBucm93ID0gbGVuZ3RoKG5ldy5jbHVzdGVycykpKQoJCWNvbG5hbWVzKGVucmljaC5kZikgPC0gYygzLCAxNywgMTAsIDExKQoJCXJvd25hbWVzKGVucmljaC5kZikgPC0gbmV3LmNsdXN0ZXJzCgkJbWV0YS5kZiA8LSBjbXAub2JqZWN0QG1ldGEuZGF0YQoJCWZvcihyb3cuaWQgaW4gcm93bmFtZXMoZW5yaWNoLmRmKSl7CgkJCXRvdC5jbHVzIDwtIG5yb3cobWV0YS5kZlttZXRhLmRmW1ttZXRhLmNvbF1dID09IHJvdy5pZCxdKQoJCQlmb3IoY29sLmlkIGluIGNvbG5hbWVzKGVucmljaC5kZikpewoJCQkJbnVtLnggPC0gbnJvdyhtZXRhLmRmWyhtZXRhLmRmW1ttZXRhLmNvbF1dID09IHJvdy5pZCkgJiAobWV0YS5kZiRjbHVzdC5JRCA9PSBjb2wuaWQpLF0pCgkJCQlwY3QueCA8LSBhcy5pbnRlZ2VyKG51bS54IC8gdG90LmNsdXMgKjEwMCkKCQkJCSMgcHJpbnQocGN0LngpCgkJCQllbnJpY2guZGZbcm93LmlkLCBjb2wuaWRdIDwtIHBjdC54CgkJCX0KCQl9CgkJY29sbmFtZXMoZW5yaWNoLmRmKSA8LSBzYXBwbHkoY29sbmFtZXMoZW5yaWNoLmRmKSwgZnVuY3Rpb24oeCkgcGFzdGUwKCJvbGRjbHVzdGVyIiwgeCkpCgkJcm93bmFtZXMoZW5yaWNoLmRmKSA8LSBzYXBwbHkocm93bmFtZXMoZW5yaWNoLmRmKSwgZnVuY3Rpb24oeCkgcGFzdGUwKCJuZXdjbHVzdGVyIiwgeCkpCgkJeGxzeDo6d3JpdGUueGxzeChlbnJpY2guZGYsIGZpbGUgPSBwYXN0ZTAoIlBjdE9mTmV3Q2x1c3RlcnNPdmVybGFwcGluZ09sZENsdXN0ZXJzXyIsIHByb2plY3ROYW1lLCAiX2RpbSIsIG5kaW1zLCAiLnhsc3giKSwgc2hlZXROYW1lID0gcGFzdGUwKGdzdWIoIlJOQV9zbm5fIiwgIiIsIG1ldGEuY29sKSksIGFwcGVuZCA9IFRSVUUpCgkJcHJpbnQoZW5yaWNoLmRmKQoJfQp9CgpgYGAKQWJzb2x1dGVseSB0ZXJyaWJsZSBvdmVybGFwLCBubyBlbnJpY2htZW50IG9mIGFueSBvZiB0aGVzZSBhY3Jvc3MgdGhlIG5ldyBjbHVzdGVyaW5nIGFsZ29yaXRobS4gTWF5YmUgc2hvdWxkIHRyeSA5NSUgdmFyaWF0aW9uIGNvdmVyZWQKCiMjIEZpbmQgb2xkIGNlbGxzIG9uIFVNQVAKCnRpbWUgZm9yIHRoZSBzdXBlciBzY2FyZXkgbW9tZW50IHRvIHNlZSBpZiB0aGUgY2VsbHMgZnJvbSBzZXVyYXR2MSBzdGlsbCBjbHVzdGVyIHRvZ2V0aGVyIG9uIGluIHNldXJhdCB2NAoKYGBge3IgZmlnLndpZHRoID0gMn0KRGltUGxvdChjbXAub2JqZWN0LAoJCQkJcmVkdWN0aW9uID0gInVtYXAiLAoJCQkJZ3JvdXAuYnkgPSAiY2x1c3QuSUQiLCAKCQkJCXB0LnNpemUgPSAuMSwKCQkJCSMgc3BsaXQuYnkgPSAib3JpZy5pZGVudCIsCgkJCQljb2xzID0gYygiZ3JheSIsICJvcmFuZ2UiLCAiYmx1ZSIsICJyZWQiLCAiZ3JlZW4iKSwpCmBgYApgYGB7ciBmaWcud2lkdGggPSA0fQpEaW1QbG90KGNtcC5vYmplY3QsCgkJCQlyZWR1Y3Rpb24gPSAidW1hcCIsCgkJCQlncm91cC5ieSA9ICJvcmlnLmlkZW50IiwgCgkJCQlzcGxpdC5ieSA9ICJjbHVzdC5JRCIsCgkJCQljb2xzID0gYygiZ3JheSIsICJvcmFuZ2UiLCAiYmx1ZSIsICJyZWQiLCAiZ3JlZW4iKSwpCmBgYAoKCgojIyMgR2VuZSBleHByZXNzaW9uIG9mIG9sZCBjbHVzdHJzIG9uIG5ldyBtYXAKTGV0J3Mgc2VlIGlmIHdlIGNhbiBnZXQgc29tZSBnZW5lIGV4cHJlc3Npb24gcHJvZmlsZXMgb24gdGhlc2UuLi4KYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xOH0KZ2VuZS5saXN0IDwtIGMoIkdhdGExIiwgIkdhdGEyIiwgIlBmNCIsICJEbnR0IiwgIk1wbyIsICJNZWlzMSIsICJJcmY4IiwgIkVsYW5lIiwgIkZsaTEiLCAiWmZwbTEiKQpWbG5QbG90KGNtcC5vYmplY3QsIGZlYXR1cmVzID0gZ2VuZS5saXN0LCBncm91cC5ieSA9ICJjbHVzdC5JRCIsIHB0LnNpemUgPSAwLjAxLCBjb2xzID0gYygiZ3JheSIsICJvcmFuZ2UiLCAiYmx1ZSIsICJyZWQiLCAiZ3JlZW4iKSkKYGBgCgoKVXNlZCB0aGUgZXhjZSBkb2MgdG8gZG8gc29tZSBmYW5jeSBjb25kaXRpb25hbCBmb3JtYXR0aW5nLiBPbGQgY2x1c3RlciAxNyBpcyBwcmV0dHkgZGlzcGVyc2VkIHVudGlsIHlvdSBpdCByZXNvbHV0aW9uIDIuNS4gT3RoZXJpc2UsIGNlbGxzIGluIG9sZCBjbHVzdGVyIDE3IGRvIG5vdCBjb25zdGl0dXRlIG1vcmUgdGhhbiA0MCUgb2YgYW55IGNlbGxzIGluIHRoZSBuZXcgY2x1c3RlcnMuICAKQXMgZmFyIGFzIEkgY2FuIHNlZSwgdGhlIHR3byBhcHByb2FjaGVzIGFyZSB0byBkbyBER0VvZiBuZXcgQ01QIHcvIHJlc29sdXRpb24gPSAyLjUsIEFORC9PUiBkbyBER2UgdXNpbmcgb2xkZXIgY2x1c3RlciBJRHMuIFN1cmUgc2VlbXMgdG8gbWFrZSBzZW5zZSB0byBkbyBib3RoLi4uCgoKIyBER0Ugdy8gcmVzb2x1dGlvbiA9IDIuNQpTdHJ0IHdpdGggY29tcGFyaW5nIGFsbCBjbHVzdGVycyBhZ2FpbnN0IGFsbCBvdGhlciBjbHVzdGVycwpXcml0ZSBvdXQgY2x1c3RlciBpbmZvCgoKY2FsY3VsYXRlIGBGaW5kQWxsTWFya2VycygpYCBmb3IgZGlmZmVyZW50IGlkZW50cyBhbmQgc2F2ZSB0byBuZXcgZmlsZQpgYGB7cn0KaWRlbnQubGlzdCA8LSBjKCJSTkFfc25uX3Jlcy4wLjUiLCAiUk5BX3Nubl9yZXMuMSIsICJSTkFfc25uX3Jlcy4xLjUiLCAiUk5BX3Nubl9yZXMuMiIsICJSTkFfc25uX3Jlcy4yLjUiLCAiY2x1c3QuSUQiKQpmb3IodGVzdGVkLmlkZW50IGluIGlkZW50Lmxpc3QpewoJSWRlbnRzKGNtcC5vYmplY3QpIDwtIHRlc3RlZC5pZGVudAoJYWxsLm1hcmtlcnMgPC0gRmluZEFsbE1hcmtlcnMoY21wLm9iamVjdCkKCXhsc3g6OndyaXRlLnhsc3goeCA9IGFsbC5tYXJrZXJzWyxjKCJhdmdfbG9nMkZDIiwgInBfdmFsX2FkaiIsICJjbHVzdGVyIiwgImdlbmUiKV0sIAoJCQkJCQkJCQkgZmlsZSA9IHBhc3RlMChwcm9qZWN0TmFtZSwgIl9GaW5kQUxMTWFya2Vyc19yZXMyLjUueGxzeCIpLCAKCQkJCQkJCQkJIHNoZWV0TmFtZSA9IHRlc3RlZC5pZGVudCwgCgkJCQkJCQkJCSBjb2wubmFtZXMgPSBUUlVFLCAKCQkJCQkJCQkJIHJvdy5uYW1lcyA9IEZBTFNFLCAKCQkJCQkJCQkJIGFwcGVuZCA9IFRSVUUpCn0KYGBgCgpDcmVhdGUgYEZpbmRBbGxNYXJrZXJzKClgIGxpc3RzIGZvciBHU0VBCmBgYHtyfQpJZGVudHMoY21wLm9iamVjdCkgPC0gIlJOQV9zbm5fcmVzLjIuNSIKcmVzLjIuNS5hbGxtYXJrZXJzIDwtIEZpbmRBbGxNYXJrZXJzKGNtcC5vYmplY3QpCmBgYAoKTWFwIEhHTkMgc3ltYm9scwpgYGB7cn0KTW91c2UySHVtYW5UYWJsZSA8LSBNb3VzZTJIdW1hbihyZXMuMi41LmFsbG1hcmtlcnMkZ2VuZSkKCkhHTkMgPC0gd2l0aChNb3VzZTJIdW1hblRhYmxlLCBNb3VzZTJIdW1hblRhYmxlJEhHTkNbbWF0Y2gocmVzLjIuNS5hbGxtYXJrZXJzJGdlbmUsIE1vdXNlMkh1bWFuVGFibGUkTUdJKV0pCmhlYWQocmVzLjIuNS5hbGxtYXJrZXJzKQpyZXMuMi41LmFsbG1hcmtlcnMkSEdOQyA8LSBIR05DCnRhaWwocmVzLjIuNS5hbGxtYXJrZXJzKQpzaWcucmVzLjIuNSA8LSByZXMuMi41LmFsbG1hcmtlcnNbcmVzLjIuNS5hbGxtYXJrZXJzJHBfdmFsX2FkaiA8PSAwLjA1LCBdCnNpZy5yZXMuMi41IDwtIHNpZy5yZXMuMi41W2MoImF2Z19sb2cyRkMiLCAiSEdOQyIsICJjbHVzdGVyIildCnNpZy5yZXMuMi41IDwtIHNpZy5yZXMuMi41WyEoc2lnLnJlcy4yLjUkSEdOQyA9PSAiIiB8IGlzLm5hKHNpZy5yZXMuMi41JEhHTkMpKSxdICMgR1NFQSB3aWxsIGZhaWwgaWYgdGhlcmUgYXJlIGFueSBibGFua3Mgb3IgTkFzIGluIHRoZSB0YWJsZQpzaWcucmVzLjIuNSA8LSBzaWcucmVzLjIuNVtdCgpgYGAKCgpgYGB7cn0KZm9yKGNsdXN0ZXIgaW4gdW5pcXVlKHNpZy5yZXMuMi41JGNsdXN0ZXIpKXsKCXByaW50KHBhc3RlKCJ3cml0aW5nIGNsdXN0ZXIiLCBjbHVzdGVyKSkKCW5ldy50YWJsZSA8LSBzaWcucmVzLjIuNVtzaWcucmVzLjIuNSRjbHVzdGVyID09IGNsdXN0ZXIsIGMoIkhHTkMiLCAiYXZnX2xvZzJGQyIpXQoJbmV3LnRhYmxlIDwtIG5ldy50YWJsZVtvcmRlcigtbmV3LnRhYmxlJGF2Z19sb2cyRkMpLCBdCgl3cml0ZS50YWJsZShuZXcudGFibGUsIGZpbGUgPSBwYXN0ZTAoIlJhbmtMaXN0X3JlczIuNV9maW5kQWxsX2hnbmMvcmVzLjIuNWNsdXN0ZXIiLCBjbHVzdGVyLCAiLnJuayIpLCBxdW90ZSA9IEZBTFNFLCByb3cubmFtZXMgPSBGQUxTRSwgY29sLm5hbWVzID0gVFJVRSwgc2VwID0gIlx0IiwgKQoJCn0KYGBgCgoKCmNhbGN1bGF0ZSBgRmluZE1hcmtlcnMoKWAgdGhhdCBkaXN0aW5ndWlzaCBlYWNoIGNsdXN0ZXIgKG1pZ2h0IG92ZXJsYWIgYmV0d2VlbiBjbHVzdGVycykKYGBge3J9CmlkZW50Lmxpc3QgPC0gYygiUk5BX3Nubl9yZXMuMC41IiwgIlJOQV9zbm5fcmVzLjEiLCAiUk5BX3Nubl9yZXMuMS41IiwgIlJOQV9zbm5fcmVzLjIiLCAiUk5BX3Nubl9yZXMuMi41IiwgImNsdXN0LklEIikKZm9yKHRlc3RlZC5pZGVudCBpbiBpZGVudC5saXN0KXsKCWZvciAoY2x1c3RlciBpbiBzb3J0KGFzLm51bWVyaWMobGV2ZWxzKGNtcC5vYmplY3RAbWV0YS5kYXRhW1t0ZXN0ZWQuaWRlbnRdXSkpKSl7CgljbHVzdGVyLm1hcmtlcnMgPC0gRmluZE1hcmtlcnMoY21wLm9iamVjdCwgaWRlbnQuMSA9IGNsdXN0ZXIpCgl4bHN4Ojp3cml0ZS54bHN4KHggPSBjbHVzdGVyLm1hcmtlcnNbLGMoImF2Z19sb2cyRkMiLCAicF92YWxfYWRqIildLCAKCQkJCQkJCQkJIGZpbGUgPSBwYXN0ZTAocHJvamVjdE5hbWUsICJfRmluZE1hcmtlcnNfIiwgZ3N1YigiUk5BX3Nubl8iLCAiIiwgdGVzdGVkLmlkZW50KSwgIi54bHN4IiksIAoJCQkJCQkJCQkgc2hlZXROYW1lID0gcGFzdGUwKCJjbHN0IiwgY2x1c3RlciksIAoJCQkJCQkJCQkgY29sLm5hbWVzID0gVFJVRSwgCgkJCQkJCQkJCSByb3cubmFtZXMgPSBUUlVFLCAKCQkJCQkJCQkJIGFwcGVuZCA9IFRSVUUpCn0KfQpgYGAKCgoKYGBge3J9CmZvciAoY2x1c3RlciBpbiBzb3J0KGFzLm51bWVyaWMobGV2ZWxzKGNtcC5vYmplY3RAbWV0YS5kYXRhJFJOQV9zbm5fcmVzLjIuNSkpKSl7CgljbHVzdGVyLm1hcmtlcnMgPC0gRmluZE1hcmtlcnMoY21wLm9iamVjdCwgaWRlbnQuMSA9IGNsdXN0ZXIpCgl4bHN4Ojp3cml0ZS54bHN4KHggPSBjbHVzdGVyLm1hcmtlcnNbLGMoImF2Z19sb2cyRkMiLCAicF92YWxfYWRqIildLCAKCQkJCQkJCQkJIGZpbGUgPSBwYXN0ZTAocHJvamVjdE5hbWUsICJfRmluZE1hcmtlcnNfcmVzMi41Lnhsc3giKSwgCgkJCQkJCQkJCSBzaGVldE5hbWUgPSBwYXN0ZTAoImNsc3QiLCBjbHVzdGVyKSwgCgkJCQkJCQkJCSBjb2wubmFtZXMgPSBUUlVFLCAKCQkJCQkJCQkJIHJvdy5uYW1lcyA9IFRSVUUsIAoJCQkJCQkJCQkgYXBwZW5kID0gVFJVRSkKfQpgYGAKCiMjIENvbWJpbmUgY2x1c3RlcnMgdGhhdCBtaWdodCByZXByZXNlbnQgb2xkIGNsdXN0ZXIgaWRzCgojIERHRSB3LyBtZXRhZGF0YSBhZ2FpbnN0IGNsdXN0LklEIGFnYWluc3QgIjAiCnJlc2V0IGlkZW50IGFzICJjbHVzdC5JRCIgYW5kIHJlcnVuIGBGaW5kQWxsTWFya2VycygpYApgYGB7cn0KCUlkZW50cyhjbXAub2JqZWN0KSA8LSAiY2x1c3QuSUQiCglhbGwubWFya2VycyA8LSBGaW5kQWxsTWFya2VycyhjbXAub2JqZWN0KQoJeGxzeDo6d3JpdGUueGxzeCh4ID0gYWxsLm1hcmtlcnNbLGMoImF2Z19sb2cyRkMiLCAicF92YWxfYWRqIiwgImNsdXN0ZXIiLCAiZ2VuZSIpXSwgCgkJCQkJCQkJCSBmaWxlID0gcGFzdGUwKHByb2plY3ROYW1lLCAiX0ZpbmRBTExNYXJrZXJzX2NsdXN0SUQueGxzeCIpLCAKCQkJCQkJCQkJIHNoZWV0TmFtZSA9ICJjbHVzdElEIiwgCgkJCQkJCQkJCSBjb2wubmFtZXMgPSBUUlVFLCAKCQkJCQkJCQkJIHJvdy5uYW1lcyA9IEZBTFNFLCAKCQkJCQkJCQkJIGFwcGVuZCA9IFRSVUUpCmBgYAoKCmBgYHtyfQojIElkZW50cyhjbXAub2JqZWN0KSA8LSAiY2x1c3QuSUQiCmZvciAoY2x1c3RlciBpbiB1bmlxdWUoY21wLm9iamVjdEBtZXRhLmRhdGEkY2x1c3QuSUQpKXsKCXByaW50KGNsdXN0ZXIpCgljbHVzdGVyLm1hcmtlcnMgPC0gRmluZE1hcmtlcnMoY21wLm9iamVjdCwgaWRlbnQuMSA9IGNsdXN0ZXIpCgl4bHN4Ojp3cml0ZS54bHN4KHggPSBjbHVzdGVyLm1hcmtlcnNbLGMoImF2Z19sb2cyRkMiLCAicF92YWxfYWRqIildLCAKCQkJCQkJCQkJIGZpbGUgPSBwYXN0ZTAocHJvamVjdE5hbWUsICJfRmluZE1hcmtlcnNfY2x1c3RJRC54bHN4IiksIAoJCQkJCQkJCQkgc2hlZXROYW1lID0gcGFzdGUwKCJvbGRjbHVzdCIsIGNsdXN0ZXIpLCAKCQkJCQkJCQkJIGNvbC5uYW1lcyA9IFRSVUUsIAoJCQkJCQkJCQkgcm93Lm5hbWVzID0gVFJVRSwgCgkJCQkJCQkJCSBhcHBlbmQgPSBUUlVFKQp9CgpgYGAKCgojIERpc3Rpbmd1aXNoaW5nIGZlYXR1cmVzIG9mIGNsdXN0ZXJzClByZXZpb3VzbHkgZGVmaW5lZCBiaW9tYXJrIGdlbmVzIGJhc2VkIG9uIFBDIGNvbnRyaWJ1dGlvbnMuIE9yaWdpbmFsIGxpc3Qgd2FzIGJhc2VkIG9uICphbGwqIG1zQWdnciwgYnV0IGxldCdzIHNlZSBob3cgQ01QIHN1YnNldCBkb2VzPwpgYGB7ciBmaWcuaGVpZ2h0ID0gMzAsIGZpZy53aWR0aD02fQpWaXpEaW1Mb2FkaW5ncyhjbXAub2JqZWN0LCBkaW1zID0gMToxMCwgbmZlYXR1cmVzID0gMzAsIHJlZHVjdGlvbiA9ICJwY2EiLCBuY29sID0gMikKYGBgCgpgYGB7cn0KcGNhLmRmIDwtIGNtcC5vYmplY3RbWyJwY2EiXV0KcGNhLmRmIDwtIGFzLmRhdGEuZnJhbWUoYXMubWF0cml4KHNsb3Qob2JqZWN0ID0gcGNhLmRmLCBuYW1lID0gImZlYXR1cmUubG9hZGluZ3MiKSkpCnByaW50KGNtcC5vYmplY3RbWyJwY2EiXV0sIGRpbXMgPSAyLCBuZmVhdHVyZXMgPSA1KQpyb3duYW1lcyhwY2EuZGZbcGNhLmRmJFBDXzIgJWluJSBzb3J0KHBjYS5kZiRQQ18yLCBkZWNyZWFzaW5nID0gVFJVRSlbMTo1XSwgXSkKcm93bmFtZXMocGNhLmRmW3BjYS5kZiRQQ18yICVpbiUgc29ydChwY2EuZGYkUENfMilbMTo1XSwgXSkKYGBgCgpub3cgd2UgY2FuIGdldCBhIGxpc3Qgb2YgcHJpbmNpcGFsIGNvbXBvbmVudHMhICAKZmlyc3QgcHVsbCB0aGUgbGlzdCBvZiBvbGRBbmFseXNpcyBDTVAgdG9wIFBDIGdlbmVzCmBgYHtyfQpjbXAuYmlvbWFyayA8LSByZWFkLnRhYmxlKGZpbGUgPSAiL1VzZXJzL2hldXN0b25lZi9EZXNrdG9wL0NNUFN1YnBvcHMvQmlvTWFyay9Qcm9iZVBhbmVscy9DTVBfUENUb3BHZW5lcy50eHQiLCBzZXAgPSAiXHQiLCBoZWFkZXIgPSBUUlVFKQpiaW9tYXJrLmNtcHRhcmdldHMgPC0gYygpCmZvcihkZi5jb2wgaW4gMTpuY29sKGNtcC5iaW9tYXJrKSl7CgliaW9tYXJrLmNtcHRhcmdldHMgPC0gYyhiaW9tYXJrLmNtcHRhcmdldHMsIGJpb21hcmtbLGRmLmNvbF0pCn0KcHJpbnQoY29sbmFtZXMoYmlvbWFyaykpCnByaW50KHBhc3RlKCJ0b3RhbCBnZW5lIGNvdW50OiIsIGxlbmd0aChiaW9tYXJrLmNtcHRhcmdldHMpKSkKYGBgCgpOb3cgZ2V0IHRoZSBsaXN0IG9mIGN1cnJlbnQgcGMgZ2VuZSB0cmdldHMgKG9sZEFuYWx5c2lzIHVzZWQgbmRpbSA9IDE6Niwgc28gd2UnbGwgc3RhcnQgd2l0aCB0aGF0IHJhbmdlKQpgYGB7cn0KcGMubGlzdCA8LSBjKCJQQ18xIiwgIlBDXzIiLCAiUENfMyIsICJQQ180IiwgIlBDXzUiLCAiUENfNiIpCnBjLmdlbmVzIDwtIGxhcHBseShwYy5saXN0LCBmdW5jdGlvbih4KSByb3duYW1lcyhwY2EuZGZbcGNhLmRmW1t4XV0gJWluJSBzb3J0KHBjYS5kZltbeF1dLCBkZWNyZWFzaW5nID0gVFJVRSlbMTozMF0sXSkpICN0YXJnZXRpbmcgcm91Z2hseSAxODAgZ2VuZXMgbGlrZSBpbiBiaW9tYXJrLmNtcHRhcmdldHMKcGMuZ2VuZXMgPC0gdW5pcXVlKHVubGlzdChwYy5nZW5lcykpCnByaW50KHBhc3RlKCJ0b3RhbCBnZW5lIGNvdW50OiIsIGxlbmd0aChwYy5nZW5lcykpKQpgYGAKCk5vdyBjb21wYXJlIHRoZSBsaXN0cywgSSBndWVzczoKCmBgYHtyfQojIHNldGRpZmYoeCx5KSBnaXZlcyB5b3UgdGhpbmdzIGluIHggbm90IGluIHkuIHNldGRpZmYoeSx4KSBnaXZlcyB5b3UgdGhpbmdzIGluIHkgbm90IGluIHgKc2V0ZGlmZihiaW9tYXJrLmNtcHRhcmdldHMsIHBjLmdlbmVzKQojIHByaW50KHBhc3RlKCJcbiBsZW5ndGg6IiwgbGVuZ3RoKHNldGRpZmYoYmlvbWFyay5jbXB0YXJnZXRzLCBwYy5nZW5lcykpKSkKd3JpdGVMaW5lcyhjKCIiLCAibGVuZ3RoOiIsIGxlbmd0aChzZXRkaWZmKGJpb21hcmsuY21wdGFyZ2V0cywgcGMuZ2VuZXMpKSkpCmBgYApVbW0sIHllYWggdGhhdCB3ZW50IGtpbmRhIGhvdyBJIGV4cGVjdGVkLiBMZXQncyBkbyB0aGlzIGFnYWluLCBidXQgZm9yIHRoZSBhY3R1YWwgYmlvbWFyayBnZW5lIGxpc3RzLgpgYGB7cn0KYmlvbWFyayA8LSByZWFkLnRhYmxlKGZpbGUgPSAiL1VzZXJzL2hldXN0b25lZi9EZXNrdG9wL0NNUFN1YnBvcHMvQmlvTWFyay9Qcm9iZVBhbmVscy9CaW9tYXJrUHJvYmVMaXN0LnR4dCIsIHNlcCA9ICJcdCIpCmJpb21hcmsgPC0gYmlvbWFya1ssMV0Kc2V0ZGlmZihiaW9tYXJrLCBwYy5nZW5lcykKd3JpdGVMaW5lcyhjKCIiLCAibGVuZ3RoOiIsIGxlbmd0aChzZXRkaWZmKGJpb21hcmssIHBjLmdlbmVzKSkpKQpgYGAKCgpXaGF0IGlmIHdlIGluY3JlYXNlIHRoZSBudW1iZXIgb2YgcGNzIGJ1dCBkZWNyZWFzZSB0aGUgZGVwdGggb2YgZWFjaD8gVGhpcyBtaWdodCBjb3ZlciBtb3JlIG9mIGBiaW9tYXJrCWAsIHdoaWNoIHdhcyBvcmlnaW5hbGx5IGRldmVsb3BlZCB1c2luZyBtc0FnZ3IgaW5zdGVhZCBvZiBvbmx5IHRoZSBDTVAgc3Vic2V0CmBgYHtyfQpwYy5saXN0IDwtIGMoIlBDXzEiLCAiUENfMiIsICJQQ18zIiwgIlBDXzQiLCAiUENfNSIsICJQQ182IiwgIlBDXzciLCAiUENfOCIsICJQQ185IiwgIlBDXzEwIikKcGMuZ2VuZXMgPC0gbGFwcGx5KHBjLmxpc3QsIGZ1bmN0aW9uKHgpIHJvd25hbWVzKHBjYS5kZltwY2EuZGZbW3hdXSAlaW4lIHNvcnQocGNhLmRmW1t4XV0sIGRlY3JlYXNpbmcgPSBUUlVFKVsxOjIwXSxdKSkKcGMuZ2VuZXMgPC0gdW5pcXVlKHVubGlzdChwYy5nZW5lcykpCnByaW50KHBhc3RlKCJ0b3RhbCBnZW5lIGNvdW50OiIsIGxlbmd0aChwYy5nZW5lcykpKQoKYGBgCmBgYHtyfQpzZXRkaWZmKGJpb21hcmssIHBjLmdlbmVzKQp3cml0ZUxpbmVzKGMoIiIsICJsZW5ndGg6IiwgbGVuZ3RoKHNldGRpZmYoYmlvbWFyaywgcGMuZ2VuZXMpKSkpCmBgYAoKRm9yIGNvbXBhcmlzb24sIGxldCdzIGp1c3Qgc2VlIGhvdyBtYW55IG9mIGBiaW9tYXJrLmNtcHRhcmdldHNgIHdlcmUgYWN0dWFsbHkgaW5jbHVkZWQgaW4gYGJpb21hcmtgCmBgYHtyfQpzZXRkaWZmKGJpb21hcmsuY21wdGFyZ2V0cywgYmlvbWFyaykKd3JpdGVMaW5lcyhjKCIiLCAibGVuZ3RoOiIsIGxlbmd0aChzZXRkaWZmKGJpb21hcmsuY21wdGFyZ2V0cywgcGMuZ2VuZXMpKSkpCmBgYApgYGB7cn0KbGVuZ3RoKGJpb21hcmspIC0gbGVuZ3RoKHNldGRpZmYoYmlvbWFyaywgYmlvbWFyay5jbXB0YXJnZXRzKSkKYGBgCmBgYHtyfQpsZW5ndGgoYmlvbWFyaykgLSBsZW5ndGgoc2V0ZGlmZihiaW9tYXJrLCBwYy5nZW5lcykpCmBgYApTbyB3aGVuIHlvdSBsb29rIGF0IGl0IGxpa2UgdGhhdCwgaXQncyBub3QgYWN0dWFsbHkgdGhhdCBmYXIgb2ZmLgoKCldoYXQgYXJlIHRoZSBzaW1pbGFyaXRpZXM/OgpgYGB7cn0Kc2V0ZGlmZihzZXRkaWZmKGJpb21hcmssIGJpb21hcmsuY21wdGFyZ2V0cyksIHNldGRpZmYoYmlvbWFyaywgcGMuZ2VuZXMpKQpgYGAKVGhlc2UgYXJlIGdlbmVzIGZyb20gdGhlIDk3cHJvYmVzIG5vdCBpbiB0aGUgb2xkIENNUCBzZXQgdGhhdCBhcmUgYWxzbyBub3QgaW4gdGhlIG5ldyBDTVAgc2V0LiBPdGhlciB0aGFuIEl0Z2EyYiAod2hpY2ggaXMgYSBmYWlsZWQgcHJvYmUgYW55d2F5KSwgbm90aGluZyBzY3JlYW1zLiBBbHNvIHdlJ2QgaGF2ZSB0aHJvd24gRmx0MyBhbmQgQ2QzNCBmb3IgaW4gYW55d2F5IGJlY2F1c2UgdGhleSdyZSByZXF1aXNpdGUgY2VsbCBzdXJmYWNlIG1hcmtlcnMgKGFsc28gRmx0MyBzdXJmYWNlIG1hcmtlciBpcyBleHBlbnNpdmUgYnV0IG90aGVyd2lzZSBub3Qgbm90ZXdvcnRoeSBhbmQgbm90IHVzZWQgaW4gdGhlIGN1cnJlbnQgc29ydGluZyBzdHJhdGVneSkKCldoYXQgYWJvdXQgY2VsbCBzdXJmYWNlIG1hcmtlciBleHByZXNzaW9uPwoqIENkMzQKKiBDZDE2LzMyCiogQ2Q5CiogQ2Q0MQoqIENkNDgKKiBTY2ExIChqdXN0IHRocm93IHRoYXQgaW4gZm9yIHNoKiZzIGFuZCBnaWdnbGVzKQpgYGB7ciwgZmlnLmhlaWdodCA9IDE1LCBmaWcud2lkdGg9MTB9CnN1cmZhY2UubWFya2VycyA8LSBjKCJDZDM0IiwgIkZjZ3IzIiwgIkZjZ3IyYiIsICJDZDkiLCAiSXRnYTJiIiwgIkNkNDgiLCAiTHk2YSIpCkZlYXR1cmVQbG90KGNtcC5vYmplY3QsIGZlYXR1cmVzID0gc3VyZmFjZS5tYXJrZXJzLCBwdC5zaXplID0gMSwgc3BsaXQuYnkgPSAiY2x1c3QuSUQiLCBuY29sID0gMSkKYGBgClNhdmUgYXMgcG5nCmBgYHtyfQpwbmcoZmlsZW5hbWUgPSAiRmVhdHVyZVBsb3RfQ01QX3N1cmZhY2VNYXJrZXJzX2NsdXN0SURmYWNldC5wbmciLCBoZWlnaHQgPSAxNjAwLCB3aWR0aCA9IDE2MDApCkZlYXR1cmVQbG90KGNtcC5vYmplY3QsIGZlYXR1cmVzID0gc3VyZmFjZS5tYXJrZXJzLCBwdC5zaXplID0gMSwgc3BsaXQuYnkgPSAiY2x1c3QuSUQiLCBuY29sID0gMSkKZGV2Lm9mZigpCmBgYAoKCgoKCiMgQ2VsbCBjeWNsZSBhbmFseXNpcwpKdXN0IHNvIHdlIGtub3cgd2hhdCB3ZSdyZSB3b3JraW5nIHdpdGgKCgpgYGB7cn0Kcy5nZW5lcyA8LSBjYy5nZW5lcy51cGRhdGVkLjIwMTkkcy5nZW5lcwpnMm0uZ2VuZXMgPC0gY2MuZ2VuZXMudXBkYXRlZC4yMDE5JGcybS5nZW5lcwpgYGAKCgojIENvbXBhcmUgQCBoaWVyYXJjaGNpYWwgY2x1c3RlaXJuZwoKRG8gY2x1c3RlcmluZyB1c2luZyBiaW9tYXJrIFJOQXMgYXMgaW5wdXQKYGBge3J9CiMgUmVhZCBpbiBCaW9tYXJrUk5BcwpiaW9tYXJrLnJuYXMgPC0gcmVhZC50YWJsZSgnL1VzZXJzL2hldXN0b25lZi9EZXNrdG9wLzEwWEdlbm9taWNzRGF0YS9CaW9tYXJrUk5Bcy50eHQnKQpiaW9tYXJrLnJuYXMgPC0gYmlvbWFyay5ybmFzJFYxCmBgYAoKdXNlIGJpb21hcmsgUk5BcyB0byBkZWZpbmUgZGltZW5zaW9uYWwgcmVkdWN0aW9uCmBgYHtyfQpjbXAub2JqZWN0IDwtIHJlYWRSRFMoIkNNUF9yYXcuUkRTIikKY21wLm9iamVjdCA8LSBSdW5QQ0EoY21wLm9iamVjdCwgZmVhdHVyZXMgPSBiaW9tYXJrLnJuYXMsIG5kaW1zLnByaW50ID0gMTo1LCAsIG5mZWF0dXJlcy5wcmludCA9IDUpCkVsYm93UGxvdChjbXAub2JqZWN0LCBuZGltcyA9IDUwKQpgYGAKCgpOb3cgcnVuIHRoZSBjbHVzdGVyaW5nCmBgYHtyfQp0b3QudmFyIDwtIHBlcmNlbnQudmFyaWFuY2UoY21wLm9iamVjdEByZWR1Y3Rpb25zJHBjYUBzdGRldiwgcGxvdC52YXIgPSBGQUxTRSwgcmV0dXJuLnZhbCA9IFRSVUUpCm5kaW1zIDwtIGxlbmd0aCh3aGljaChjdW1zdW0odG90LnZhcikgPD0gOTApKQoKY21wLm9iamVjdCA8LSBGaW5kTmVpZ2hib3JzKGNtcC5vYmplY3QsIGRpbXMgPSAxOm5kaW1zKQpjbXAub2JqZWN0IDwtIEZpbmRDbHVzdGVycyhjbXAub2JqZWN0LCByZXNvbHV0aW9uID0gMC41KQpjbXAub2JqZWN0IDwtIFJ1blVNQVAoY21wLm9iamVjdCwgZGltcyA9IDE6IG5kaW1zKQoKCmBgYAoKZmluZCB0aGUgY2x1c3RlcnMKCmBgYHtyfQpmb3IoeCBpbiBjKDAuNSwgMSwgMS41LCAyLCAyLjUpKXsKCWNtcC5vYmplY3QgPC0gRmluZENsdXN0ZXJzKGNtcC5vYmplY3QsIHJlc29sdXRpb24gPSB4KQp9CmBgYAoKUGxvdCB0aGUgdW1hcHMgYW5kIGNlbGwgY2x1c3RlciBpZHMKYGBge3J9CmZvciAobWV0YS5jb2wgaW4gY29sbmFtZXMoY21wLm9iamVjdEBtZXRhLmRhdGEpKXsKCWlmKGdyZXBsKHBhdHRlcm4gPSAoIlJOQV9zbm5fcmVzIiksIHggPSBtZXRhLmNvbCk9PVRSVUUpewoJCW15cGxvdCA8LSBEaW1QbG90KGNtcC5vYmplY3QsIAoJCQkJCQkJCQkJCWdyb3VwLmJ5ID0gbWV0YS5jb2wsCgkJCQkJCQkJCQkJcmVkdWN0aW9uID0gInVtYXAiLCAKCQkJCQkJCQkJCQljb2xzID0gY29sb3IucGFsZXR0ZQoJCQkJCQkJCQkJCSkgKyAKCQkJZ2d0aXRsZShwYXN0ZTAocHJvamVjdE5hbWUsICIgZGltIiwgbmRpbXMsICJyZXMiLCBnc3ViKCJSTkFfc25uX3JlcyIsICIiLCBtZXRhLmNvbCkgKSkKCQlwbG90KG15cGxvdCkKCX0KfQpgYGAKIyMjIENhbGN1bGF0ZSBhbnRpY2lwYXRlZCBudW1iZXIgb2YgY2VsbHMgeW91J2xsIGZpbmQgaW4gZWFjaCBiaW9tYXJrIGNsdXN0ZXIKR2V0ICMgY2VsbHMgaW4gZWFjaCBjbHVzdGVyCgpgYGB7cn0KdG90LmNlbGxjb3VudCA8LSBucm93KGNtcC5vYmplY3RAbWV0YS5kYXRhKQpyZXMwNS5saXN0IDwtIHNvcnQodW5pcXVlKGNtcC5vYmplY3RAbWV0YS5kYXRhJFJOQV9zbm5fcmVzLjAuNSksIGRlY3JlYXNpbmcgPSBGQUxTRSkKc2FwcGx5KHJlczA1Lmxpc3QsIAoJCQkgZnVuY3Rpb24oeCl7CgkJCSAJcHJpbnQoCgkJCSAJCXBhc3RlKAoJCQkgCQkJImNsdXN0ZXIiLCB4LCAiPSIsIAoJCQkgCQkJbnJvdyhjbXAub2JqZWN0QG1ldGEuZGF0YVtjbXAub2JqZWN0QG1ldGEuZGF0YSRSTkFfc25uX3Jlcy4wLjUgPT0geCxdKSwgCgkJCSAJCQkiY2VsbHMgb3IiLCAKCQkJIAkJCXJvdW5kKG5yb3coY21wLm9iamVjdEBtZXRhLmRhdGFbY21wLm9iamVjdEBtZXRhLmRhdGEkUk5BX3Nubl9yZXMuMC41ID09IHgsXSkvdG90LmNlbGxjb3VudCoxMDAsIGRpZ2l0cyA9IDIpLCAKCQkJIAkJCSIlIG9mIHRvdGFsIgoJCQkgCQkpCgkJCSAJKQoJCQkgfQoJCQkpCmBgYAoKU28gd2UgZGlkIHRoZSBkaW1lbnNpb25hbCByZWR1Y3Rpb24gYmFzZWQgb24gdGhlIGJpb21hcmsgUk5BcywgdGhlbiBkaWQgb3VyIFVNQVAgbmVhcmVzdCBuZWlnaGJvciBjbHVzdGVyaW5nLgoKCkluIHRoZSBiaW9tYXJrIGhpZXJhcmNoY2lhbCBjbHVzdGVyaW5nIGFuYWx5c2lzIEkgYXNzYXllZCAxNjcgY2VsbHMuIFRoZSBzbWFsbGVzdCBjbHVzdGVyIEkgZGV0ZWN0ZWQgaGFkIDMgY2VsbHMsIG9yIDEuOCUgb2YgdG90YWwsIGFuZCB0aGlzIGlzIGFuIHVuY29tZm9ydGFibHkgc21hbGwgbnVtYmVyIG9mIGNlbGxzLiBCYXNlZCBvbiB0aGUgVU1BUCBjYWxjdWxhdGlvbnMgSSB3b3VsZCB0aGVyZWZvcmUgZXhwZWN0IHRvIGZpbmQgMTEgb3IgMTIgb2YgdGhlIHByZWRpY3RlZCAxNSBjbHVzdGVycy4gSSBmb3VuZCAxMiwgYW5kIEkgZG9uJ3QgcmVhbGx5IGxpa2UgdGhhdCBsYXN0IG9uZSwgc28gMTEgb3IgMTIuIFNpbmNlIEkgZGlkIHRoZSBoaWVyYXJjaGNpYWwgY2x1c3RlcmluZyB5ZXN0ZXJkYXkgYW5kIGRpZCB0aGlzIG1hdGggdG9kYXksIHdlIGNhbiBzYXkgaXQgd2FzIGluZGVwZW5kZW50IG9mIHRoZXNlIHJlc3VsdHMgYW5kIHRoZXJlZm9yZSB0b3RhbGx5IGxlZ2l0LiBZYXkhIQoK